// <copyright file="Polygon.cs" company="Signal Processing Lab, Wuhan University">
// Copyright (c) 2009-2010 All Right Reserved
// </copyright>
// <author>Xinping Deng</author>
// <email>dentoby@gmail.com</email>
// <date>2010-4</date>
// <summary>This is part of Image.NET project.</summary>

namespace SPLWHU.Core.Vectors
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// A Polygon is a planar Surface, defined by 1 exterior boundary and 0 or more interior boundaries. Each
    /// interior boundary defines a hole in the Polygon.
    /// </summary>
    /// <remarks>
    /// Vertices of rings defining holes in polygons are in the opposite direction of the exterior ring.
    /// </remarks>
    public class Polygon : Geometry
    {
        /// <summary>
        /// The outer boundary.
        /// </summary>
        private LinearRing outerBoundary;

        /// <summary>
        /// The inner boundaries.
        /// </summary>
        private List<LinearRing> innerBoundaries;

        /// <summary>
        /// Initializes a new instance of the <see cref="Polygon"/> class.
        /// </summary>
        public Polygon()
        {
            this.isValid = false;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Polygon"/> class by specifying an outter boundary.
        /// </summary>
        /// <param name="outer">The outer boundary.</param>
        public Polygon(LinearRing outer)
        {
            this.outerBoundary = outer;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Polygon"/> class specifying an outter boundary and an array of holes.
        /// </summary>
        /// <param name="outer">The outer boundary.</param>
        /// <param name="holes">The inner holes.</param>
        public Polygon(LinearRing outer, params LinearRing[] holes)
        {
            // the outter boundary.
            this.outerBoundary = outer;

            // the inner boundaries.
            this.innerBoundaries = new List<LinearRing>(holes.Length);
            foreach (LinearRing hole in holes)
            {
                this.innerBoundaries.Add(hole);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Polygon"/> class by copying another <see cref="Polygon"/>.
        /// </summary>
        /// <param name="copyMe">The polygon to copy.</param>
        public Polygon(Polygon copyMe)
        {
            // Copies the outter boundary.
            this.outerBoundary = (LinearRing)copyMe.outerBoundary.Clone();

            // Copies the inner holes.
            if (copyMe.innerBoundaries != null)
            {
                this.innerBoundaries = new List<LinearRing>();
                foreach (LineString hole in copyMe.GetHoles())
                {
                    this.innerBoundaries.Add((LinearRing)hole.Clone());
                }
            }
        }

        /// <summary>
        /// Gets or sets the outter boundary.
        /// </summary>
        /// <value>The outter boundary.</value>
        public LinearRing OutterBoundary
        {
            get { return this.outerBoundary; }
            set { this.outerBoundary = value; }
        }

        /// <summary>
        /// Gets the number of holes in this polygon.
        /// </summary>
        /// <value>The number of holes.</value>
        public int NumHoles
        {
            get
            {
                if (this.innerBoundaries == null)
                {
                    return 0;
                }
                else
                {
                    return this.innerBoundaries.Count;
                }
            }
        }

        /// <summary>
        /// Gets the centroid of this instance. Normally, returns the centroid of the bounding box.
        /// </summary>
        /// <value>The centroid of this instance.</value>
        public override Point2d Centroid
        {
            get { return this.GetExtents().Centroid; }
        }

        /// <summary>
        /// Gets the corresponding wkb geometry type (<see cref="GeometryType"/>).
        /// </summary>
        /// <value>An int represents the wkb geometry type.</value>
        public override int TypeFlag
        {
            get { return (int)GeometryType.Polygon; }
        }

        /// <summary>
        /// Gets the geometry count in this geometry.
        /// </summary>
        /// <value>The geometry count.</value>
        /// <remarks>
        /// A <see cref="Point2d"/> or <see cref="LineString"/> instance return zero.
        /// </remarks>
        public override int GeometryCount
        {
            get
            {
                if (this.innerBoundaries != null)
                {
                    return this.innerBoundaries.Count + 1;
                }
                else
                {
                    return 1;
                }
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="SPLWHU.Core.Vectors.LinearRing"/> at the specified index.
        /// </summary>
        /// <value>The line ring at the specified postion.</value>
        public LinearRing this[int index]
        {
            get
            {
                if (index == 1)
                {
                    return this.outerBoundary;
                }
                else if (this.innerBoundaries != null)
                {
                    return this.innerBoundaries[index - 1];
                }
                else
                {
                    return null;
                }
            }

            set
            {
                if (index == 1)
                {
                    this.outerBoundary = value;
                }
                else if (this.innerBoundaries != null)
                {
                    this.innerBoundaries[index - 1] = value;
                }
            }
        }

        /// <summary>
        /// Gets the geometry in this instance at the specified index.
        /// </summary>
        /// <param name="index">The geometry index.</param>
        /// <returns>The geometry at the specified position.</returns>
        public override Geometry GetGeometry(int index)
        {
            if (index == 0)
            {
                return this.outerBoundary;
            }
            else if (index > 0)
            {
                return this.innerBoundaries[index - 1];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Creates a new geometry and adds it to this instance.
        /// </summary>
        /// <returns>The geometry created.</returns>
        public override Geometry CreateGeometry()
        {
            if (this.outerBoundary == null)
            {
                this.outerBoundary = new LinearRing();
                return this.outerBoundary;
            }
            else
            {
                if (this.innerBoundaries == null)
                {
                    this.innerBoundaries = new List<LinearRing>();
                }

                // Creates a new ring.
                LinearRing hole = new LinearRing();
                this.innerBoundaries.Add(hole);

                return hole;
            }
        }

        /// <summary>
        /// Gets the bounding box of this geometry.
        /// </summary>
        /// <returns>The bounding box of this geometry.</returns>
        public override BoundingBox GetExtents()
        {
            BoundingBox box = this.outerBoundary.GetExtents();

            if (this.innerBoundaries == null)
            {
                return box;
            }

            // Gets the joint of all LinearRings.
            foreach (LineString ring in this.innerBoundaries)
            {
                box = box.Joint(ring.GetExtents());
            }

            return box;
        }

        /// <summary>
        /// Creates a copy of this geometry.
        /// </summary>
        /// <returns>The copy of this geometry.</returns>
        public override Geometry Clone()
        {
            return new Polygon(this);
        }

        /// <summary>
        /// Gets the hole at the specified index.
        /// </summary>
        /// <param name="index">The index of the hole.</param>
        /// <returns>The hole at the specified position.</returns>
        public LinearRing GetHole(int index)
        {
            if (this.innerBoundaries != null && this.innerBoundaries.Count > index && index >= 0)
            {
                return this.innerBoundaries[index];
            }
            else
            {
                throw new VectorException("Can not access the hole at {0}", index);
            }
        }

        /// <summary>
        /// Adds a inner hole to this polygon.
        /// </summary>
        /// <param name="hole">The hole to add.</param>
        public void AddHole(LinearRing hole)
        {
            if (this.innerBoundaries == null)
            {
                this.innerBoundaries = new List<LinearRing>();
            }

            this.innerBoundaries.Add(hole);
        }

        /// <summary>
        /// Removes the specified hole from this polygon.
        /// </summary>
        /// <param name="hole">The hole to be removed.</param>
        public void RemoveHole(LinearRing hole)
        {
            if (this.innerBoundaries != null)
            {
                this.innerBoundaries.Remove(hole);
            }
        }

        /// <summary>
        /// Removes the hold at the specified index.
        /// </summary>
        /// <param name="index">The index of the hole to remove.</param>
        public void RemoveHoldAt(int index)
        {
            try
            {
                this.innerBoundaries.RemoveAt(index);
            }
            catch (Exception ex)
            {
                throw new VectorException(ex.Message);
            }
        }

        /// <summary>
        /// Clears all the holes.
        /// </summary>
        public void ClearHoles()
        {
            if (this.innerBoundaries != null)
            {
                this.innerBoundaries.Clear();
                this.innerBoundaries = null;
            }
        }

        /// <summary>
        /// Gets enumeration of the inner holes. Note that the enumeration can not updated, so try to modify the reference of this enumrator will cause an exception.
        /// </summary>
        /// <returns>The enumeration of all holes</returns>
        public IEnumerable<LinearRing> GetHoles()
        {
            foreach (LinearRing hole in this.innerBoundaries)
            {
                yield return hole;
            }
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            try
            {
                // Firstly, the object must be a Polygon.
                Polygon polygon = obj as Polygon;
                if (polygon == null)
                {
                    return false;
                }

                // Secondly, the outter boundary must be equal.
                if (this.outerBoundary != polygon.outerBoundary)
                {
                    return false;
                }

                // Thirdly, the count of inner boundaries must be equal.
                if (this.innerBoundaries.Count != polygon.innerBoundaries.Count)
                {
                    return false;
                }

                // At last, each inner boundaries must be equal.
                for (int i = 0, count = this.innerBoundaries.Count; i < count; i++)
                {
                    if (this.innerBoundaries[i] != polygon.innerBoundaries[i])
                    {
                        return false;
                    }
                }

                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            int hash = this.outerBoundary.GetHashCode();

            foreach (LinearRing hole in this.innerBoundaries)
            {
                hash = hash ^ hole.GetHashCode();
            }

            return hash;
        }
    }
}
